/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package message

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"gitee.com/tugoer/orivil/wechat"
	"net/http"
	"strings"
	"sync"
	"time"
)

// 消息类型(包括被动消息类型及客服消息类型)
type ServerMsgType string

const (
	ServerMsgTypeText  ServerMsgType = "text"
	ServerMsgTypeImage ServerMsgType = "image"
	ServerMsgTypeVoice ServerMsgType = "voice"
	ServerMsgTypeVideo ServerMsgType = "video"
	ServerMsgTypeMusic ServerMsgType = "music"
	ServerMsgTypeNews  ServerMsgType = "news"
)

// ServerMessage 是客服消息模板, 需要用户触发过菜单事件, 或发送过消息到服务器, 且必须在 48 小时之内回复用户,
// 该消息由本地服务器主动发送消息到微信服务器, 再由微信服务器发送到用户微信客户端
type ServerMessage struct {
	ToUser  string        `json:"touser"` // 用户 openid
	MsgType ServerMsgType `json:"msgtype"`
	Text    *Text         `json:"text"`
	Image   *Media        `json:"image"`
	Voice   *Media        `json:"voice"`
	Video   *Media        `json:"video"`
	Music   *Music        `json:"music"`
	News    *News         `json:"news"`
	MpNews  *Media        `json:"mpnews"`
	WxCard  *WxCard       `json:"wxcard"`
	locker  sync.Mutex
}

//48001	api 功能未授权，请确认公众号已获得该接口，可以在公众平台官网 - 开发者中心页中查看接口权限
//48002	粉丝拒收消息（粉丝在公众号选项中，关闭了 “ 接收消息 ” ）
//48004	api 接口被封禁，请登录 mp.weixin.qq.com 查看详情
//48005	api 禁止删除被自动回复和自定义菜单引用的素材
//48006	api 禁止清零调用次数，因为清零次数达到上限
//48008	没有该类型消息的发送权限

// 判断是否是终止错误, 即所有同类型的消息都不可发送. 有可能某一个公众号被封号, 导致 API 功能受限, 出现大量错误信息
func IsBreakError(err error) bool {
	if we, ok := err.(*wechat.Error); ok {
		if 48001 <= we.ErrCode && we.ErrCode <= 48008 {
			return true
		}
	}
	return false
}

func (m *ServerMessage) ReplaceWithStrings(oldnew ...string) {
	replacer := strings.NewReplacer(oldnew...)
	m.ReplaceWithReplacer(replacer)
}

func (m *ServerMessage) ReplaceWithReplacer(replacer *strings.Replacer) {
	if m.Text != nil {
		m.Text.Replace(replacer)
	}
	if m.News != nil {
		m.News.Replace(replacer)
	}
}

func (m *ServerMessage) Send(token, toUser string) error {
	m.locker.Lock()
	defer m.locker.Unlock()
	if toUser != "" {
		m.ToUser = toUser
	}
	u := "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + token
	buf := bytes.NewBuffer(nil)
	encoder := json.NewEncoder(buf)
	encoder.SetEscapeHTML(false)
	err := encoder.Encode(m)
	if err != nil {
		return err
	}

	return wechat.PostJson(u, m, nil)
}

type WxCard struct {
	ID string `json:"card_id"`
}

type Music struct {
	Title        string `json:"title"`
	Description  string `json:"description"`
	Musicurl     string `json:"musicurl"`
	Hqmusicurl   string `json:"hqmusicurl"`
	ThumbMediaID string `json:"thumb_media_id"`
}

type Text struct {
	Content string `json:"content"`
}

func (t *Text) Replace(replacer *strings.Replacer) {
	t.Content = replacer.Replace(t.Content)
}

type Media struct {
	ID           string `json:"media_id"`
	ThumbMediaID string `json:"thumb_media_id"`
	Title        string `json:"title"`
	Description  string `json:"description"`
}

type News struct {
	Articles []*Article `json:"articles"`
}

func (ns *News) Replace(replacer *strings.Replacer) {
	for _, article := range ns.Articles {
		article.Url = replacer.Replace(article.Url)
	}
}

type Article struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	Url         string `json:"url"`
	PicUrl      string `json:"picurl"`
}

// 被动回复消息, 当用户触发微信事件时, 微信服务器将会 POST 一个消息到本地服务器, 本地服务器可在 5 秒之内立即返回消息到微信服务器,
// 微信服务器再将收到的消息转发到用户微信客户端
type EventMessage struct {
	XMLName      xml.Name      `xml:"xml"`
	ToUserName   string        // 接收方帐号（收到的OpenID）
	FromUserName string        // 开发者微信号(原始ID)
	CreateTime   int64         // 消息创建时间 （整型）

	MsgType      ServerMsgType // text|image|voice|video|music|news

	Content EvtText `xml:",omitempty"`

	Image *EvtImage `xml:",omitempty"`

	Voice *EvtVoice `xml:",omitempty"`

	Video *EvtVideo `xml:",omitempty"`

	Music *EvtMusic `xml:",omitempty"`

	ArticleCount int       `xml:",omitempty"`
	Articles     *Articles `xml:"Articles>item,omitempty"` // 图文消息信息，注意，如果图文数超过限制，则将只发限制内的条数
}

type Articles []Article

type EvtText string

type EvtImage struct {
	MediaId string
}

type EvtVoice struct {
	MediaId string
}

type EvtVideo struct {
	MediaId string
	Title       string `xml:",omitempty"` // 标题(可选)
	Description string `xml:",omitempty"` // 描述(可选)
}

type EvtMusic struct {
	MediaId string
	Title       string `xml:",omitempty"` // 标题(可选)
	Description string `xml:",omitempty"` // 描述(可选)
	MusicURL     string `xml:",omitempty"` // 音乐连接
	HQMusicUrl   string `xml:",omitempty"` // 高质量音乐链接，WIFI环境优先使用该链接播放音乐
	ThumbMediaId string `xml:",omitempty"` // 缩略图的媒体id，通过素材管理中的接口上传多媒体文件，得到的id
}

func (em *EventMessage) Response(createTime int64, writer http.ResponseWriter) error {
	if em.MsgType == "" {
		// 假如服务器无法保证在五秒内处理并回复，必须做出下述回复，这样微信服务器才不会对此作任何处理，并且不会发起重试（这种情况
		// 下，可以使用客服消息接口进行异步回复），否则，将出现严重的错误提示。详见下面说明：
		//
		// 1、直接回复success（推荐方式） 2、直接回复空串（指字节长度为0的空字符串，而不是XML结构体中content字段的内容为空）
		writer.Write([]byte("success"))
	} else {
		if em.Articles != nil {
			em.ArticleCount = len(*em.Articles)
		}
		em.CreateTime = time.Now().Unix()
		buf := bytes.NewBuffer(nil)
		err := xml.NewEncoder(buf).Encode(em)
		if err != nil {
			return err
		}
		writer.Header().Set("Content-Type", "application/xml;charset=UTF-8")
		_, err = writer.Write(buf.Bytes())
		return err
	}
	return nil
}
